由于webpack基于发布订阅模式，在运行的生命周期中会广播出许多事件，插件通过监听这些事件
，就可以在特定的阶段执行自己的插件任务


webpack编译会创建两个核心对象
compiler：包含了 webpack 环境的所有的配置信息，包括 options，loader 和 plugin，和 webpack 整个生命周期相关的钩子
compilation：作为 plugin 内置事件回调函数的参数，包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。
当检测到一个文件变化，一次新的 Compilation 将被创建

如果自己要实现plugin，也需要遵循一定的规范：

插件必须是一个函数或者是一个包含 apply 方法的对象，这样才能访问compiler实例
传给每个插件的 compiler 和 compilation 对象都是同一个引用，因此不建议修改
异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程，不然会卡住

<script>
//plugins/MyPlugin.js
class MyPlugin {
  constructor(options) {//传递给plugin的参数，options获取配置信息，对插件做一些初始化的工作。
    console.log("Plugin被创建了",options);
  }
  apply (compiler) {//它会在webpack运行时被调用，并且注入compiler对象
    //不推荐使用，plugin函数被废弃了
    // compiler.plugin("compile", (compilation) => {
    //   console.log("compile");
    // });
    //注册完成的钩子
    compiler.hooks.emit.tap("MyPlugin", (compilation) => {
      //compiler不仅有同步的钩子，通过tap函数来注册，还有异步的钩子，通过tapAsync和tapPromise来注册：
      console.log("compilation done");
    });
    //compiler对象包含了 Webpack 环境所有的的配置信息，代表了整个webpack从启动到关闭的生命周期
    //compilation对象包含了当前的模块资源、编译生成资源、变化的文件等，compilation只是代表了一次新的编译过程
  }
}
module.exports = MyPlugin;
</script>
在 emit 事件发生时，代表源文件的转换和组装已经完成，可以读取到最终将输出的资源、
代码块、模块及其依赖，并且可以修改输出资源的内容

其工作流程如下：
webpack启动，执行new myPlugin(options)，初始化插件并获取实例
初始化complier对象，调用myPlugin.apply(complier)给插件传入complier对象
插件实例获取complier，通过complier监听webpack广播的事件，通过complier对象操作webpack
